﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Inspire.DataStructures;
using Inspire.Entities;
using Inspire.Utility;
using System.Text.RegularExpressions;
using Inspire.Classes.Common;

namespace Inspire.Classes.Business
{
    /// <summary>
    /// Business class for handling database interaction related to Patrons.
    /// </summary>
    public class PatronBusiness
    {
        /// <summary>
        /// The single instance of PatronBusiness.
        /// </summary>
        private static PatronBusiness patronBusinessSingleton = null;

        /// <summary>
        /// Reference to the CustomFieldBusiness singleton.
        /// </summary>
        private CustomFieldBusiness _customFieldBusiness = null;

        /// <summary>
        /// Static object used for locking the GetPatronBusiness() method so no two threads can
        /// call the method simultaneously (creating two instances of this class). Synchronization
        /// is required to implement the Singleton pattern correctly.
        /// </summary>
        private static object syncLock = new object();

        /// <summary>
        /// Get a reference to our persistence context.
        /// </summary>
        private InspireEntities _context;

        /// <summary>
        /// Private constructor. This is going to be a Singleton.
        /// </summary>
        private PatronBusiness()
        {
            this._context = ContextProvider.GetContextProvider().GetContext();

            this._customFieldBusiness = CustomFieldBusiness.GetCustomFieldBusiness();
        }

        /// <summary>
        /// Method used for obtaining the Singleton instance of this class.
        /// </summary>
        /// <returns>PatronBusiness</returns>
        public static PatronBusiness GetPatronBusiness()
        {
            lock (syncLock)
            {
                if (PatronBusiness.patronBusinessSingleton == null)
                {
                    PatronBusiness.patronBusinessSingleton = new PatronBusiness();
                }

                return PatronBusiness.patronBusinessSingleton;
            }
        }

        /// <summary>
        /// Adds a new patron to the Persistence Context along with the custom fields provided.
        /// </summary>
        /// <param name="newPatron">The Patron object containing the information of the new Patron.</param>
        /// <param name="customFields">A collection of the custom fields to be added for this Patron.</param>
        /// <returns></returns>
        public Patron AddPatron(Patron newPatron, Collection<NewCustomFieldSource> customFields, Collection<PatronContactMethod> contactFields)
        {
            try
            {
                // Set the new ID
                newPatron.Id = _context.People.NextId(f => f.Id);

                // Save the new object to the database
                this._context.People.AddObject(newPatron);

                // Add the custom fields to the database
                foreach (NewCustomFieldSource nextEntry in customFields)
                {
                    this._customFieldBusiness.SaveNewCustomField(nextEntry.CustomFieldType, nextEntry.FieldValue, newPatron.Id);
                }

                // Add the contact fields to the database
                foreach (PatronContactMethod nextEntry in contactFields)
                {
                    this._customFieldBusiness.SaveNewCustomField(nextEntry.MethodFieldType, nextEntry.MethodValue, newPatron.Id);
                }

                // Persist all Changes
                _context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
            }
            catch (Exception ex)
            {
                // Handle Save Errors
                Console.Out.WriteLine(ex.Message);
                Console.Out.WriteLine(ex.StackTrace);

                return null;
            }

            return newPatron;
        }

        /// <summary>
        /// Saves the state of an existing Patron.
        /// </summary>
        /// <param name="patron">The existing Patron.</param>
        /// <param name="customFields">Custom fields to be saved.</param>
        /// <param name="contactFields">Contact fields to be saved.</param>
        /// <returns>The saved Patron or null if the save failed.</returns>
        public Patron SavePatron(Patron patron, Collection<NewCustomFieldSource> customFields, Collection<PatronContactMethod> contactFields)
        {
            try
            {
                // Save the changes -- Detach the original, then attach the revised version
                this._context.People.Detach((Patron)this._context.GetObjectByKey(patron.EntityKey));
                this._context.People.Attach(patron);
                this._context.ObjectStateManager.ChangeObjectState(patron, System.Data.EntityState.Modified);

                // Save the custom fields
                foreach (NewCustomFieldSource nextEntry in customFields)
                {
                    if (nextEntry.ExistingField == null)
                    {
                        this._customFieldBusiness.SaveNewCustomField(nextEntry.CustomFieldType, nextEntry.FieldValue, patron.Id);
                    }
                    else
                    {
                        // Update the value of the CustomField from the NewCustomFieldSource object
                        nextEntry.ExistingField.SetData(nextEntry.FieldValue);
                    }
                }

                // Save the contact fields
                foreach (PatronContactMethod nextEntry in contactFields)
                {
                    if (nextEntry.ExistingField == null)
                    {
                        this._customFieldBusiness.SaveNewCustomField(nextEntry.MethodFieldType, nextEntry.MethodValue, patron.Id);
                    }
                    else
                    {
                        // Update the value of the CustomField from the PatronContactMethod
                        nextEntry.ExistingField.SetData(nextEntry.MethodValue);
                    }
                }

                // Persist all changes
                this._context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
            }
            catch (Exception ex)
            {
                // Handle Save Errors
                Console.Out.WriteLine(ex.Message);
                Console.Out.WriteLine(ex.StackTrace);

                return null;
            }

            return patron;
        }

        /// <summary>
        /// Find a Patron by the ID.
        /// </summary>
        /// <param name="patronId">The Patron's ID to search on.</param>
        /// <returns>(Patron) The Patron whose ID matches the one provided, or NULL if no patron is found or if more than 1 patron is found.</returns>
        public Patron GetPatron(int? patronId)
        {
            IEnumerable<Patron> resultSet;

            // Check for a null
            if (patronId == null)
            {
                return null;
            }

            // Find the patron
            resultSet = from patrons in _context.People.OfType<Patron>()
                        where patrons.Id == patronId
                        select patrons;

            // If more than 1 patron is found or if no patron is found, return null
            if (resultSet.Count() != 1)
            {
                return null;
            }

            // Return the Patron
            return resultSet.ElementAt(0);
        }

        

        /// <summary>
        /// Find the first 50 patrons with first or last names beginning with the first/last name provided to this method. (e.g. "Clin" would match "Clint").
        /// </summary>
        /// <param name="firstName">The characters to search for a first name on.</param>
        /// <param name="lastName">The characters to search for a last name on.</param>
        /// <returns>List of matching Patrons.</returns>
        public IEnumerable<Patron> SearchByName(string firstName, string lastName)
        {
            if (firstName.Length > 0 && lastName.Length > 0)
            {
                return (from patrons in _context.People.OfType<Patron>() 
                        where patrons.FirstName.ToLower().StartsWith(firstName.ToLower()) && patrons.LastName.ToLower().StartsWith(lastName.ToLower())
                        select patrons).Take(50);
            }
            else if (firstName.Length > 0)
            {
                return (from patrons in _context.People.OfType<Patron>()
                        where patrons.FirstName.ToLower().StartsWith(firstName.ToLower())
                        select patrons).Take(50);
            }
            else if (lastName.Length > 0)
            {
                return (from patrons in _context.People.OfType<Patron>()
                        where patrons.LastName.ToLower().StartsWith(lastName.ToLower())
                        select patrons).Take(50);
            }

            return null;
        }
    }
}
